﻿namespace RLab.Visualization

open System
open System.Drawing
open System.Windows.Forms
open Microsoft.DirectX
open Microsoft.DirectX.Direct3D

open GeometryDefinitions

type View = 
    { YawPitchRoll: Matrix;
      Focus       : Vector3;
      Zoom        : float }
    with
        member view.AdjustZoom(dx,dy) = 
            { view with Zoom = view.Zoom * exp (float dy / 100.0) } 

        member view.AdjustYawPitchRoll(dx,dy) = 
            let rx = float32 dx / 20.0f
            let ry = float32 dy / 20.0f    
            let m = Matrix.RotationYawPitchRoll(ry,0.0f,rx)
            let m = transformAt ((X1 + Y1 + Z1) *  -0.5f ) m
            { view with YawPitchRoll = m * view.YawPitchRoll }

        member view.AdjustFocus(dx,dy) = 
            let dv = Y1 * (float32 (-dx) / 2.0f)  + Z1 * (float32 dy / 2.0f) 
            { view with Focus = view.Focus + dv }
    end

type CameraManipulator(c : Control) =
    let mutable view = 
       { YawPitchRoll   = Matrix.RotationYawPitchRoll(0.0f,60.5f,pi/ 1.7f)
         Focus          = scale 1.0f ( X1 + ( -145.0f * Y1 ) + ( 140.0f * Z1 ) )
         Zoom           = 750.0 }

    let event = new Event<_>() 
    let lastArgs = ref None
    do c.MouseDown.Add(fun args -> lastArgs := Some args)
    do c.MouseUp.Add  (fun args -> lastArgs := None)
    do c.MouseMove.Add(fun args -> 
        match !lastArgs with
        | Some last -> event.Trigger(last,args); lastArgs := Some args
        | None -> ());
    do event.Publish.Add( fun (a,b) -> 
                        let view2 = 
                            let dx = b.X - a.X
                            let dy = b.Y - a.Y
                            match b.Button, Form.ModifierKeys with 
                            | MouseButtons.Left, Keys.Shift -> view.AdjustZoom(dx,dy)
                            | MouseButtons.Left, _          -> view.AdjustYawPitchRoll(dx,dy)
                            | _                             -> view.AdjustFocus(dx,dy)  
                        view <- view2)
    with 
        member x.View = 
            let eye = (X1 + Y1 + Z1) * 2.0f - X1 * single view.Zoom
            Matrix.Invert(view.YawPitchRoll) * Matrix.LookAtLH(eye,view.Focus,Z1)


type Renderer(c : Control) as x = 
    let drawEvent = new Event<_>()
    let viewMan = new CameraManipulator(c)
    let invert (a,b) = (b,a)
    let surface (c : Color) n step  = 
        [| for i in -n..n do
             let xz = (float32)i*step,(float32)n*step
             let horiz (x,z) = Vector3(x,0.0f,z) 
             yield xz |> horiz
             yield (fst xz, -(snd xz)) |> horiz
             yield xz |> invert |> horiz
             yield (fst xz, -(snd xz)) |> invert |> horiz |] 
        |> Array.map (fun v -> CustomVertex.PositionNormalColored(v,X1 ,c.ToArgb()))
    let whiteSurface = surface Color.White 5 100.0f

    let mutable resources : IDisposable list = List.Empty    

    let pp = new PresentParameters(Windowed = true, SwapEffect = SwapEffect.Copy,AutoDepthStencilFormat = DepthFormat.D16)
    do pp.EnableAutoDepthStencil <- true

    let device = new Device(0,DeviceType.Hardware,c,CreateFlags.SoftwareVertexProcessing,pp)
    let on_reset_device () =            
        device.RenderState.CullMode <- Cull.None
    let vbsurface() =
        let b = new VertexBuffer(typeof<CustomVertex.PositionNormalColored>, 100, device, Usage.WriteOnly, CustomVertex.PositionNormalColored.Format, Pool.Default)
        do b.SetData(whiteSurface, 0,LockFlags.None)
        resources <- List.append resources [b]
        b
    let drawSurface() =
        device.RenderState.Lighting <- false                
        device.SetStreamSource(streamNumber=0,streamData=vbsurface(),offsetInBytes=0)                 
        device.VertexFormat <- CustomVertex.PositionNormalColored.Format
        device.DrawPrimitives(PrimitiveType.LineList, 0, whiteSurface.Length)
    let setLights() =  
        let mat() = 
            let mutable m = new Material()
            m.Ambient <- Color.FromArgb(255,255,255,255)
            m.Diffuse <- Color.FromArgb(255,255,255,255)
            m.Emissive <- Color.FromArgb(0,0,0,0)
            m.Specular <- Color.FromArgb(0,0,0,0)
            m.SpecularSharpness <- 1.0f
            m  
        device.Material <- mat()
        device.Lights.[0].Enabled <- true
        device.Lights.[0].Diffuse <- Color.FromArgb(0,255,255,255)
        device.Lights.[0].Ambient <- Color.FromArgb(0,0,0,0)
        device.Lights.[0].Specular <- Color.FromArgb(0,0,0,0)
        device.Lights.[0].Type <-LightType.Directional
        device.Lights.[0].Direction <- new Vector3(-2.0f,-0.2f,-0.1f)
        device.Lights.[0].Position <- new Vector3(-10.0f,0.0f,0.0f)
        device.Lights.[0].Attenuation0 <- 0.0f
        device.Lights.[0].Attenuation1 <- 0.1f
        device.Lights.[0].Attenuation2 <- 0.0f
        device.Lights.[0].Range <- 50.0f
        device.Lights.[0].Enabled <- true
        device.RenderState.Ambient <- System.Drawing.Color.FromArgb(0x202020)
        device.RenderState.Lighting <- true
    let setCamera () =  
        device.Transform.World <- Matrix.Identity  
        device.Transform.View <- viewMan.View       
        device.Transform.Projection <- Matrix.PerspectiveFovLH( pi / 4.0f,1.0f, 1.0f, 9500.0f );

    do device.DeviceReset.Add(fun _ -> on_reset_device() )
    do c.Paint.Add ( fun _ -> x.Render())
    do on_reset_device()

    member x.DrawScene = drawEvent.Publish
    member x.CreateMeshBox (dimenstions : Vector3) = 
        Mesh.Box(device,dimenstions.X,dimenstions.Y,dimenstions.Z)
    member x.CreateMeshCylinder rad len = 
        Mesh.Cylinder(device,rad,rad,len,16,1)
    member x.LoadMeshFromFile(path) =
        try
            let m = Mesh.FromFile(path,MeshFlags.Managed,device)
            resources <- List.append resources [m]
            m
        with e ->
            failwithf "Mesh %s not found" path
    member x.Render() =        
        if( not device.Disposed ) then       
            device.Clear (ClearFlags.Target ||| ClearFlags.ZBuffer, Color.Gray, 1.0f, 0)
            device.BeginScene()
            setCamera()
            drawSurface()
            setLights()
            drawEvent.Trigger(device)
            device.EndScene()
            device.Present()
        
    member x.MeshScale =  Matrix.Scaling(0.3f,0.3f,0.3f )

    interface IDisposable with
        member x.Dispose() = 
            device.Dispose()
            resources |> List.iter ( fun re -> re.Dispose())


    


